package com.bird.component.log

import cn.hutool.core.util.ArrayUtil
import com.bird.component.lang.util.log
import com.bird.component.sensitive.toJacksonJsonStr
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.core.annotation.AnnotatedElementUtils
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import java.lang.reflect.Method
import java.util.*

@Aspect
@Component
class WebLogAspect {
    //伴生对象
    companion object{
        /** 换行符  */
        private val LINE_SEPARATOR: String = System.lineSeparator()
    }
    /** 以自定义 @WebLog 注解为切点  */
    @Pointcut("@annotation(com.bird.component.log.WebLog)")
    fun webLog(){}
    /**
     * 日志打印
     * 返回方法名称
     */
    private fun beforeLog(joinPoint: JoinPoint): String {
        // 开始打印请求日志
        val (currentMethod, classInfo) = getCurrentMethod(joinPoint)
        //只有controller入口处，才打印request信息
        if (AnnotatedElementUtils.hasAnnotation(currentMethod, RequestMapping::class.java)) {
            val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes
            if (Objects.nonNull(attributes)) {
                val request = attributes.request
                // 打印请求 url
                log.info("[URL]            : {}", request.requestURL.toString())
                // 打印 Http method
                log.info("[HTTP Method]    : {}", request.method)
                // 打印请求的 IP
                log.info("[IP]             : {}", request.remoteAddr)
            }
        }
        // 获取 @WebLog 注解的描述信息
        val methodDescription = getAspectLogDescription(currentMethod, classInfo)
        val annotation = currentMethod.getAnnotation(WebLog::class.java)
        val excludeParams: Array<String> = annotation.excludeParams
        // 打印请求相关参数
        log.info("{}|====================== Start ==========================================", methodDescription)
        // 打印描述信息
        log.info("[Description]    : {}", methodDescription)
        // 打印调用 controller 的全路径以及执行方法
        log.info("[Class Method]   : {}.{}", joinPoint.signature.declaringTypeName, joinPoint.signature.name)
        // 打印请求入参--入参敏感数据加密或者忽略
        log.info("{}|----- 入参处理----------------start----------------------+$LINE_SEPARATOR", methodDescription)
        val args = joinPoint.args
        if (ArrayUtil.isNotEmpty(joinPoint.args)) {
            val parameters = currentMethod.parameters
            if (args.size == parameters.size) {
                for (i in args.indices) {
                    if (ArrayUtil.isEmpty(excludeParams)
                        || !ArrayUtil.contains(excludeParams, parameters[i].name)
                    ) {
                        var lastStr = ""
                        if (i == (args.size - 1)) {
                            lastStr = LINE_SEPARATOR
                        }
                        log.info(
                            "    [参数{}]    ~~~    {} : {}{}",
                            (i + 1),
                            parameters[i].name,
                            args[i].toJacksonJsonStr(),
                            lastStr
                        )
                    }
                }
            } else {
                log.info("    [Request Args]: {}", joinPoint.args.toJacksonJsonStr())
            }
        }
        log.info("{}|----- 入参处理----------------end----------------------", methodDescription)
        return methodDescription
    }

    /**
     * 在切点之后织入
     */
    private fun afterLog(methodInfo: String) {
        // 接口结束后换行，方便分割查看
        log.info("{}|=======================End ===========================================$LINE_SEPARATOR", methodInfo)
    }

    private fun getCurrentMethod(joinPoint: JoinPoint):Pair<Method,String>{
        try {
            val sig = joinPoint.signature
            require(sig is MethodSignature) { "该注解只能用于方法" }
            val target = joinPoint.target
            val aClass: Class<*> = target.javaClass
            val annotation:WebLogClass? = aClass.getAnnotation(WebLogClass::class.java)
            return Pair(aClass.getMethod(sig.name, *sig.parameterTypes), annotation?.value ?: "")
        } catch (e: NoSuchMethodException) {
            log.error("获取当前方法出错")
            throw com.bird.component.lang.common.exception.ServiceException("获取当前方法出错")
        }
    }

    /**
     * 环绕
     * @return 真正的结果
     */
    @Around("webLog()")
    @Throws(Throwable::class)
    fun doAround(proceedingJoinPoint: ProceedingJoinPoint): Any {
        val methodInfo = beforeLog(proceedingJoinPoint)
        val startTime = System.currentTimeMillis()
        val result = proceedingJoinPoint.proceed()
        if (result is Optional<*> && result.isPresent) {
            // 打印出参--出参敏感数据加密或者忽略
            log.info("{}|--Response Args  : {}", methodInfo, result.get().toJacksonJsonStr())
        } else {
            // 打印出参--出参敏感数据加密或者忽略
            log.info("{}|--Response Args  : {}", methodInfo, result.toJacksonJsonStr())
        }
        // 执行耗时
        log.info("{}|--Time-Consuming : {} ms", methodInfo, System.currentTimeMillis() - startTime)
        afterLog(methodInfo)
        return result
    }


    /**
     * 获取切面注解的描述
     *
     * @param method 当前方法
     * @return 描述信息
     */
    fun getAspectLogDescription(method: Method, title: String): String {
        return title
            .let {
                if (it.isNotBlank()) {
                    "『$title』--"
                } else ""
            } +
                method.getAnnotation(WebLog::class.java).value +
                method.getAnnotation(WebLog::class.java).description
    }
}